﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using Tomahawk.Serialization.SerialBoost;
using Tomahawk.Serialization.Reflection;
using System.Xml.Serialization;

namespace arcC
{
    public class ArchetypeCompiler
    {
        /// <summary>
        /// Search for possible archetyp in content folders and compile them
        /// </summary>
        public void Compile(string assemblyFile, List<String> ContentFolders, string extension)
        {
            //load the mother assembly
            this.loadAssemblyAndDependences(assemblyFile);

            //index serializers manually
            foreach (Assembly asm in this.loadedAssemblies)
            {
                SerialBoost.Instance.IndexSerializerInAsm(asm);
            }

            //scan all content folders
            int totalFiles = 0;
            foreach (String folder in ContentFolders)
            {
                Console.Write( String.Format("Scaning folder {0}...", folder));
                int filesFound = this.scanContentFolder(folder, extension);
                totalFiles+=filesFound;
                Console.WriteLine( String.Format("{0} files found!", filesFound));
            }

            //write summary
            Console.WriteLine("Done scaning folders, possible archetypes found: " + totalFiles);
            
            //compile each file
            foreach (FileInfo fi in this.archetypeList)
            {
                this.compileArchetype(fi.FullName);
            }
        }

        #region "Detecting types on the input assembly"

        private List<Assembly> loadedAssemblies;

        /// <summary>
        /// Load and assembly and all it's DLL dependecies
        /// </summary>
        private Assembly loadAssemblyAndDependences(string assemblyPath)
        {
            if (this.loadedAssemblies == null) this.loadedAssemblies = new List<Assembly>();

            //load the assembly
            Assembly asm = Assembly.LoadFrom(assemblyPath);

            //referenced assemblies
            AssemblyName[] refAsm = asm.GetReferencedAssemblies();
            for (int i = 0; i < refAsm.Length; i++)
            {
                //search in the list
                bool alreadyLoaded = false;
                for (int j = 0; j < this.loadedAssemblies.Count; j++)
                {
                    if (this.loadedAssemblies[j].FullName == refAsm[i].FullName)
                    {
                        alreadyLoaded = true;
                        break;
                    }
                }

                if (!alreadyLoaded)
                {
                    //compose the name of the file assembly
                    string asmName = refAsm[i].Name;
                    System.IO.FileInfo fi = new System.IO.FileInfo(assemblyPath);
                    string refDllPath = System.IO.Path.Combine(fi.DirectoryName, asmName) + ".dll";
                    string refExePath = System.IO.Path.Combine(fi.DirectoryName, asmName) + ".exe";

                    //exists the exe or dll file in the target folder?
                    string refPath = "";
                    if (System.IO.File.Exists(refDllPath)) refPath = refDllPath;
                    if (System.IO.File.Exists(refExePath)) refPath = refExePath;

                    //recursivelly load the assembly
                    if (refPath != "")
                    {
                        Assembly loadedRefAsm = this.loadAssemblyAndDependences(refPath);
                        if (loadedRefAsm != null)
                        {
                            this.loadedAssemblies.Add(loadedRefAsm);
                        }
                    }
                }
            }

            //add the asm itselft to the list
            this.loadedAssemblies.Add(asm);

            //return loaded asm
            return asm;
        }

        #endregion

        #region "Archetype files list"

        private List<FileInfo> archetypeList = null;

        /// <summary>
        /// Scan a content folder recursivelly and add all .xml files found to a list of possible archetypes.
        /// </summary>
        private int scanContentFolder(string folder, string extension)
        {
            int foundFiles = 0;

            if (archetypeList == null) archetypeList = new List<FileInfo>();

            System.IO.DirectoryInfo dirInfo = new DirectoryInfo(folder);
            
            //process subfolders
            DirectoryInfo[] subdirs = dirInfo.GetDirectories();
            foreach (DirectoryInfo subdirInfo in subdirs)
            {
                foundFiles += this.scanContentFolder(subdirInfo.FullName, extension);
            }

            //get files
            string extensionMask = "*." + extension;
            FileInfo[] files = dirInfo.GetFiles(extensionMask);
            foreach (FileInfo file in files)
            {
                if (!file.FullName.EndsWith("c"))
                {
                    foundFiles++;
                    archetypeList.Add(file);
                }
                else
                {
                    System.IO.File.Delete(file.FullName);
                }
            }

            return foundFiles;
        }

        #endregion

        #region "Archetype Compiling"

        private void compileArchetype(string p)
        {
            try
            {
                Console.Write("Compiling archetype: " + p + "...");

                //read the file
                string fileData = System.IO.File.ReadAllText(p);

                //get the compiler directive for correct type
                string type = guessObjectType(ref fileData);

                if (type == "") throw new Exception("Archetype compile directive not found!");

                //get the type
                Type t = AssemblyIndexer.Instance.GetTypeFromAssemblies(type);
                if (t == null) throw new Exception("Invalid type " + type + " not found!");

                //load the archetype using XmlSerialize
                SerialBoost.ForceAllUseXmlSerializer = true;
                XmlSerializer xmlSer = new XmlSerializer(t);
                StringReader stR = new StringReader(fileData);
                object obj = xmlSer.Deserialize(stR);
                SerialBoost.ForceAllUseXmlSerializer = false;

                //serialize using BoostSerialize
                MemoryStream ms = SerialBoost.Instance.Serialize(obj);

                //save the memorystream to output file
                FileStream fs = new FileStream(p + "c", FileMode.Create, FileAccess.Write);
                fs.Write(ms.GetBuffer(), 0, (int)ms.Length);
                fs.Flush();
                fs.Close();
                ms.Close();

                Console.WriteLine("Done!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR!");
                //TODO: Add to a list of errors to write a briefing at the end
            }
        }

        /// <summary>
        /// Return the object type
        /// </summary>
        private string guessObjectType(ref string fileData)
        {
            string retVal = "";
            int directiveStart = fileData.IndexOf("<!--ARCC", 0);
            if (directiveStart > 0)
            {
                int directiveEnd = fileData.IndexOf("-->", directiveStart);
                retVal = fileData.Substring(directiveStart + 10, directiveEnd - (directiveStart + 10));
            }
            return retVal;
        }

        #endregion
    }

}
